# -*- coding: utf-8 -*-
#	Copyright 2005,2006,2007,2008 Spike^ekipS <spikeekips@gmail.com>
#
#	   This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os, datetime, threading
import lucene

from django.dispatch import dispatcher
from django.db.models import signals
from django.conf import settings
from django.db.models import options as options_django, fields as fields_django

import search as django_searcher

QUERY_BOOLEANS = {
	"AND"	: lucene.BooleanClause.Occur.MUST,
	"OR"	: lucene.BooleanClause.Occur.SHOULD,
	"NOT"	: lucene.BooleanClause.Occur.MUST_NOT,
	False	: lucene.BooleanClause.Occur.MUST,
	True	: lucene.BooleanClause.Occur.MUST_NOT,
}

QUERY_OPERATORS = {
	"AND": lucene.QueryParser.Operator.AND,
	"OR" : lucene.QueryParser.Operator.OR,
}

MAXINT = int(2**31-1)

######################################################################
# Fields to lucene fields
# string
FIELDS_STR = (
	fields_django.OrderingField,
	fields_django.TextField,
	fields_django.CharField,
	fields_django.NullBooleanField,
	fields_django.SlugField,
	fields_django.XMLField,
)

# int
FIELDS_INT = (
	fields_django.SmallIntegerField,
	fields_django.PositiveSmallIntegerField,
	fields_django.PositiveIntegerField,
	fields_django.IntegerField,
	fields_django.FloatField,
	fields_django.AutoField,
	fields_django.BooleanField,
	fields_django.DecimalField,
)


# meta
FIELDS_META = (
	fields_django.USStateField,
	fields_django.URLField,
	fields_django.CommaSeparatedIntegerField,
	fields_django.EmailField,
	fields_django.IPAddressField,
	fields_django.PhoneNumberField,
)

# date
FIELDS_DATE = (
	fields_django.TimeField,
	fields_django.DateField,
	fields_django.DateTimeField,
)
# path
FIELDS_PATH = (
	fields_django.FileField,
	fields_django.FilePathField,
	fields_django.ImageField,
)

def parse_query (query) :
	qparser = lucene.QueryParser("", lucene.StandardAnalyzer())
	qparser.setDefaultOperator(lucene.QueryParser.Operator.AND)

	return qparser.parse(query)

class BooleanQuery (lucene.BooleanQuery) :
	def __init__ (self) :
		super(BooleanQuery, self).__init__()
		self.setMaxClauseCount(MAXINT)

class TermQuery (lucene.PhraseQuery) :
	def __init__ (self, term=None) :
		super(TermQuery, self).__init__()
		if isinstance(term, lucene.Term) :
			for t in term.text().split() :
				self.add(lucene.Term(term.field(), t))

######################################################################
# indexing & searching
class LuceneCore (object) :
	searcher = None
	reader = None
	writer = None

	def __init__ (self) :
		self.write_lock = threading.Lock()
		self.open_storage()

	def __del__ (self) :
		try : self.close()
		except : pass

	def get_analyzer(self) :
		return lucene.StandardAnalyzer()

	def open_writer (self, create=False) :
		if self.writer :
			self.writer.close()

		created = os.path.isfile(
			os.path.join(settings.SEARCH_STORAGE_PATH, "segments.gen")
		)
		self.writer = lucene.IndexWriter(
			self.storage,
			self.get_analyzer(),
			create and True or not created,
		)

	def open_reader (self) :
		if self.reader :
			self.reader.close()

		try :
			self.reader = lucene.IndexReader.open(self.storage)
		except :
			pass

	def open_searcher (self) :
		if self.searcher :
			self.searcher.close()

		self.searcher = lucene.IndexSearcher(self.storage)

	def open_storage (self) :
		if os.path.isdir(settings.SEARCH_STORAGE_PATH) :
			created = True
		else :
			created = False
			try :
				os.mkdir(settings.SEARCH_STORAGE_PATH)
			except OSError, e :
				raise OSError, e

		self.storage = lucene.FSDirectory.getDirectory(
			settings.SEARCH_STORAGE_PATH,
			not created,
		)

	def close (self) :
		self.close_writer()
		self.close_searcher()
		self.close_reader()
		self.close_storage()

	def clean (self) :
		self.open_storage()

		self.write_lock.acquire()
		self.open_writer(True)
		self.write_lock.release()

		self.close()

	def close_writer (self) :
		try: self.writer.close()
		except : pass
		self.writer = None

	def close_searcher (self) :
		try: self.searcher.close()
		except : pass
		self.searcher = None

	def close_reader (self) :
		try: self.reader.close()
		except : pass
		self.reader = None

	def close_storage (self) :
		try : self.storage.close()
		except :pass
		self.storage = None

class Searcher (LuceneCore) :

	def get_document_by_uid (self, uid) :
		query = BooleanQuery()
		query.add(lucene.TermQuery(lucene.Term(django_searcher.FIELD_NAME_UID, uid)), QUERY_BOOLEANS.get(False))
		try :
			return list(self.search(query))[0]
		except :
			return None

	def get_hits (self, query, sort=lucene.Sort.RELEVANCE, slice=None) :
		_open_searcher = False
		if self.searcher is None :
			_open_searcher = True
			self.open_searcher()

		try :
			hits = self.searcher.search(query, sort)
		except SystemError :
			hits = self.searcher.search(query, lucene.Sort.RELEVANCE)

		if _open_searcher :
			self.close_searcher()

		return hits

	def iterator (self, query, sort=lucene.Sort.RELEVANCE, slice=None) :
		self.open_searcher()

		hits = self.get_hits(query, sort=sort, slice=slice, )
		#print
		#print "[II] ", query
		i = 0
		if slice and slice.start :
			i = slice.start
		while True :
			if slice and slice.stop and i >= slice.stop :
				break
			try:
				yield hits[i]
			except lucene.JavaError :
				break

			i += 1

		self.close_searcher()

	search = iterator

class Indexer (LuceneCore) :
	created = False

	def __init__ (self, object, optimize=False,) :
		self.object = object
		self.optimizing = optimize
		self.fields = django_searcher.get_fields_from_model(self.object.__class__)

		super(Indexer, self).__init__()

	def optimize (self) :
		self.write_lock.acquire()

		self.open_writer()
		self.writer.optimize()
		self.close_writer()

		self.write_lock.release()

	def unindex (self) :
		self.open_reader()

		if self.reader is None:
			return self

		t = lucene.Term(django_searcher.FIELD_NAME_UID, django_searcher.get_object_uid(self.object))
		self.write_lock.acquire()
		self.reader.deleteDocuments(t)

		if self.reader.hasDeletions() :
			#print "removed"
			pass
		else:
			#print "un-removed"
			pass

		self.close_reader()
		self.write_lock.release()

		return self

	def index (self, update=False, ) :
		# get indexed document
		searcher = Searcher()
		doc = searcher.get_document_by_uid(django_searcher.get_object_uid(self.object))
		if doc is not None :
			self.unindex() # delete previous index

		self.open_writer()

		self.write_lock.acquire()
		try :
			doc = lucene.Document()
			fields = list()

			# store model and uid
			fields.append(
				lucene.Field(
					django_searcher.FIELD_NAME_MODEL,
					django_searcher.get_model_name(self.object.__class__),
					lucene.Field.Store.YES,
					lucene.Field.Index.UN_TOKENIZED,
				)
			)
			fields.append(
				lucene.Field(
					django_searcher.FIELD_NAME_UID,
					django_searcher.get_object_uid(self.object),
					lucene.Field.Store.YES,
					lucene.Field.Index.UN_TOKENIZED,
				)
			)

			fields.append(
				lucene.Field(
					django_searcher.FIELD_NAME_PK,
					str(self.object.pk),
					lucene.Field.Store.YES,
					lucene.Field.Index.UN_TOKENIZED,
				)
			)

			for f in self.fields :
				field_id = f.name
				value = getattr(self.object, f.name)
				store = True
				tokenize = True

				if f.__class__ in FIELDS_STR :
					fields.extend(django_searcher.create_field(f, value, store, tokenize, add_sort=True, ))
				elif f.__class__ in FIELDS_INT :
					tokenize = False
					fields.extend(django_searcher.create_field(f, value, store, tokenize, add_sort=True, ))
				elif f.__class__ in FIELDS_DATE :
					tokenize = False
					fields.extend(django_searcher.create_field(f, value, store, tokenize, add_sort=True, ))
				elif f.__class__ in FIELDS_META :
					fields.extend(django_searcher.create_field(f, value, store, tokenize, add_sort=True, ))
				elif f.__class__ in FIELDS_PATH :
					tokenize = False
					fields.extend(django_searcher.create_field(f, value, store, tokenize, add_sort=True, ))
				else :
					# it's related field,
					pass

			for f in fields :
				doc.add(f)

			self.writer.addDocument(doc)
		finally :
			self.close_writer()

		self.write_lock.release()

		if self.optimizing :
			self.optimize()

		return self

"""
Description
-----------


ChangeLog
---------


Usage
-----


"""

__author__ =  "Spike^ekipS <spikeekips@gmail.com>"




